#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "stm32f4xx.h"
#include "debug.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

#define CONSOLE_TX      GPIO_Pin_9
#define CONSOLE_RX      GPIO_Pin_10
#define CONSOLE_PORT    USART1

static usart_rx_callback_t rx_callback;
static SemaphoreHandle_t rx_binary;

static void debug_uart_init(void) 
{
    USART_ITConfig(CONSOLE_PORT, USART_IT_RXNE, ENABLE); 
    
    USART_InitTypeDef USART_InitStructure;
    memset(&USART_InitStructure, 0, sizeof(USART_InitTypeDef));
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;

    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
    USART_Init(CONSOLE_PORT, &USART_InitStructure);
    USART_Cmd(CONSOLE_PORT, ENABLE);
}

static void debug_io_init(void) 
{
    GPIO_InitTypeDef GPIO_InitStructure;
    memset(&GPIO_InitStructure, 0, sizeof(GPIO_InitTypeDef));

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Pin = CONSOLE_TX | CONSOLE_RX; 
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure); 

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); 
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); 
}

static void debug_irq_init(void) 
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    NVIC_InitTypeDef NVIC_InitStructure;
    memset(&NVIC_InitStructure, 0, sizeof(NVIC_InitTypeDef));
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_Init(&NVIC_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn;
    NVIC_Init(&NVIC_InitStructure);
}

static void debug_dma_init(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC8;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Low;
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_ITConfig(DMA2_Stream7, DMA_IT_TC, ENABLE);
    DMA_Init(DMA2_Stream7, &DMA_InitStructure);
}

void debug_init(void) 
{
    rx_binary = xSemaphoreCreateBinary();
    configASSERT(rx_binary);

    debug_uart_init();
    debug_dma_init();
    debug_irq_init(); 
    debug_io_init();  
}

void debug_recv_callback_register(usart_rx_callback_t cb)
{
    rx_callback = cb;
}

void USART1_IRQHandler(void) 
{
    if(USART_GetITStatus(CONSOLE_PORT, USART_IT_RXNE) == SET) 
    {
        uint8_t data = USART_ReceiveData(CONSOLE_PORT);
        if(rx_callback != NULL)
        {
            rx_callback(data);
        }
        USART_ClearITPendingBit(CONSOLE_PORT, USART_IT_RXNE);
    }
}

void debug_usart_sendbyte(const char *data, uint32_t len)
{
    do
    {
        uint32_t chunk = len < 65535 ? len : 65535;
        
        DMA_Cmd(DMA2_Stream7, DISABLE);
        DMA2_Stream7->M0AR = (uint32_t)data;
        DMA2_Stream7->NDTR = chunk;
        DMA_ClearFlag(DMA2_Stream7, DMA_FLAG_TCIF7);
        DMA_Cmd(DMA2_Stream7, ENABLE);
        xSemaphoreTake(rx_binary, portMAX_DELAY);

        data += chunk;
        len -= chunk;
    }while (len > 0);

    while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
    USART_ClearFlag(USART1, USART_FLAG_TC);
}

int fputc(int ch, FILE *p)
{
    USART_SendData(CONSOLE_PORT, (uint8_t)ch);
    while (USART_GetFlagStatus(CONSOLE_PORT, USART_FLAG_TC) == RESET);
    return ch;
}

void DMA2_Stream7_IRQHandler(void)
{
   if (DMA_GetITStatus(DMA2_Stream7, DMA_IT_TCIF7) != RESET)
   {
       BaseType_t pxHigherPriorityTaskWoken;
       xSemaphoreGiveFromISR(rx_binary, &pxHigherPriorityTaskWoken);
       portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
       DMA_ClearITPendingBit(DMA2_Stream7, DMA_IT_TCIF7);
   }
}
